#include <string>
#include <boost/iostreams/device/file_descriptor.hpp>
#include <boost/iostreams/stream.hpp>
#include <boost/scoped_ptr.hpp>

#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <cstdlib>
#include <cstring>
#include <cerrno>
#include <stdexcept>

#define TREETAGGER_ENVVAR "TREETAGGER_COMMAND"
// when will boost::process be out ?

class TreeTagger {
	typedef boost::iostreams::file_descriptor_source fd_device;
	typedef boost::iostreams::stream<fd_device> fd_stream_type;
	pid_t treetagger_pid;
	pid_t feeder_pid;
	boost::scoped_ptr<fd_stream_type> output;
public:	
	struct DoesntWorkException : public std::runtime_error {
		DoesntWorkException(const std::string& e):
			std::runtime_error(e) {}
	};
	TreeTagger() : treetagger_pid(-1), feeder_pid(-1) {}
	void wait_and_close() {
		if (treetagger_pid >= 0)
			waitpid(treetagger_pid, NULL, 0);
		treetagger_pid = -1;
		if (feeder_pid >= 0)
			waitpid(feeder_pid, NULL, 0);
		feeder_pid = -1;
		output.reset();
	}
	static void fail_loudly(int error_code, const std::string errormess,
			bool raise=true) {
		if (error_code < 0) {
			std::ostringstream errorstream;
			errorstream << errormess << " : " << strerror(errno);
			std::cerr << errorstream.str() << std::endl;
			if (!raise)
				return;
			throw DoesntWorkException("OS error occured :" 
					+ errorstream.str());
		}
	}
	fd_stream_type& get_stream() { return *output; }
	void start_treetagger(std::string string);
	~TreeTagger() {
		wait_and_close();
	}
};


// will be great when Boost.Process gets out. i can't wait ! 
void TreeTagger::start_treetagger(std::string input) {
	if (treetagger_pid >= 0 || feeder_pid >= 0)
		throw std::logic_error("treetagger already running");
	int input_pipe[2];
	int output_pipe[2];
	class CloseItWhenFinished {
		int fd;
	public:
		CloseItWhenFinished(int _fd) : fd(_fd) {}
		void close(bool loud = true) {
			if (fd < 0)
				return;
			TreeTagger::fail_loudly(::close(fd), "failed to close"
					" a pipe", loud);
			fd = -1;
		}
		int release() {
			int ret = fd;
			fd = -1;
			return ret;
		}
		~CloseItWhenFinished() { close(false); }
	};
				
	fail_loudly(pipe(input_pipe), "failed creating the input pipe");
	CloseItWhenFinished input_entry(input_pipe[1]),
				standart_input(input_pipe[0]);
	fail_loudly(pipe(output_pipe), "failed creating the output pipe");
	CloseItWhenFinished standart_output(output_pipe[1]),
				output_retrieval(output_pipe[0]);
	treetagger_pid = fork();
	if (!treetagger_pid) {
		input_entry.close();
		output_retrieval.close();
		fail_loudly(dup2(standart_input.release(), STDIN_FILENO),
				"duping treetagger input failed");
		fail_loudly(dup2(standart_output.release(), STDOUT_FILENO),
				"duping treetagger output failed");
		
		const char* ttenv = getenv(TREETAGGER_ENVVAR);
		if (ttenv) {
			std::string cmd = std::string(ttenv);
			TreeTagger::fail_loudly(execl(cmd.c_str(), NULL),
				"launching "+cmd+" failed", false);
		} else {
			TreeTagger::fail_loudly(execlp("tree-tagger-french",
				NULL), "launching tree-tagger-french failed",
					false);
		}
		exit(1);
	} else if (treetagger_pid < 0)
		fail_loudly(treetagger_pid, "forking a treetagger process");
	standart_input.close();
	standart_output.close();
	feeder_pid = fork();
	if (!feeder_pid) {
		output_retrieval.close();
		typedef boost::iostreams::file_descriptor_sink device;
		typedef boost::iostreams::stream<device> stream_type;
		stream_type stream(device(input_entry.release(),
				boost::iostreams::close_handle));

		stream << input;
		stream.close();
		exit(0);
	} else if (feeder_pid < 0)
		fail_loudly(feeder_pid, "forking a treetagger feeder process");

	input_entry.close();
	output.reset(new fd_stream_type(fd_device(output_retrieval.release(),
			boost::iostreams::close_handle)));
}
